#pylint: disable-msg=W0212

from django.conf import settings
from django.utils.encoding import iri_to_uri
from django.core.cache import cache
from django.http import HttpResponse
from django.utils.cache import patch_response_headers
from django.db.models import Q

def positive(value):
    try:
        ret=int(value)
        if ret<=0:
            raise ValueError()
    except ValueError:
        raise
    return ret

def get_attr(obj, attr, default=None):
    """Recursive get object's attribute. May use dot notation.

    >>> class C(object): pass
    >>> a = C()
    >>> a.b = C()
    >>> a.b.c = 4
    >>> get_attr(a, 'b.c')
    4
    """
    if '.' not in attr:
        return getattr(obj, attr, default)
    else:
        L=attr.split('.')
        return get_attr(getattr(obj, L[0], default), '.'.join(L[1:]), default)

class BadRequestError(Exception):
    pass

class AjaxService(object):
    """
    Usage:
    service = AjaxService(Model.objects.all(), ['id','email'], ['email__istartswith'])
    service1 = AjaxService(Model.objects.all(), ['id','field1','fk.field1',...], ['full_name__icontains'], required=['q'])
    eervice2 = AjaxService(Model.objects.all(), ['id','field1'],  ['full_name__icontains'], "field3='f'", required=['q'])
    
    in urls.py
        (r'^ajax/test_service/', 'service')  
        
    
    Parameters:
    
            fields       - fields to return
            filter       - master filter rule, must be accepted by filter method of queryset
            required     - list of required (also empty) parameters
            extra filter - comma separated hardcoded extra filter 
    
    Examples:
        service = AjaxService(Model.objects.all(), ['id','email'], 'email__istartswith')

    """
    LIMIT=10
    MAX_ALLOWED_LIMIT_VALUE=100
    DEBUG=True
    def __init__(self, queryset, fields, filters, extrafilter={}, required=[], distinct=False, formatter=None, **kwargs):
        self.fields=fields
        self.distinct=distinct
        self.filters=filters
        #self.extrafilter =  (extrafilter and dict([ a.split("=") for a in extrafilter.split(",")])) or {}
        self.extrafilter=extrafilter
        self.limit=min(AjaxService.LIMIT, AjaxService.MAX_ALLOWED_LIMIT_VALUE)
        self.formatter=formatter
        self.queryset=queryset # do not remove is used by ajax widgets
        self.timeout=60
        self.required=required
        self.__name__='<AjaxService: (%s)>'%str(queryset._as_sql()[0])

        for k, v in kwargs.items():
            setattr(self, k, v)

    def iter_results(self, results, fields):
        if results:
            for r in results:
                l=[]
                if self.formatter:
                    yield self.formatter(fields)
                else:
                    for f in fields:
                        v=get_attr(r, f)
                        if callable(v):
                            v=v()
                        l.append(unicode(v))
                    yield  "|".join(l)+"\n"

    def _get_filters(self, request):
        def subst(k):
            if k in ('false', 'False', False):
                return 0
            elif k in ('none', 'None'):
                return None
            elif k in ('',):
                return "__any__"
            elif k in ('true', 'True', True):
                return 1
            else:
                return k

        ret={}
        extra=[]
        ret.update(self.extrafilter)

        self.limit=AjaxService.LIMIT

        for k, value in request.GET.items():
            v=subst(value)
            if k=="q":
                k=self.filters

            if v=="__any__":
                continue
            elif v==None:
                if k==self.filters:
                    for f in k:
                        if "__" not in f:
                            ret["%s__isnull"%f]=True
            elif k=="timestamp":
#                #TODO handle cache
                pass
            elif k=="limit":
                self.limit=min(positive(v), AjaxService.MAX_ALLOWED_LIMIT_VALUE)
            else:
                if k==self.filters:
                    flt=Q()
                    for f in k:
                        flt=flt|Q(**{ f: v})
                    extra.append(flt)
                else:
                    ret[str(k)]=str(v)
        return ret, extra

    def learn_cache_key(self, request, cache_timeout=None, key_prefix=None):
        if key_prefix is None:
            key_prefix=settings.CACHE_MIDDLEWARE_KEY_PREFIX
        if cache_timeout is None:
            cache_timeout=settings.CACHE_MIDDLEWARE_SECONDS
        entry='views.ajax.cache.%s.%s.%s'%(iri_to_uri(request.path),
                                            "-".join(request.GET.values()),
                                            "-".join(self.fields)
                                            )
        return entry

    def _check_required(self, request):
        if not set(request.GET.keys()).issuperset(self.required):
            raise BadRequestError('Missing Required arguments %s'%self.required)

    def handle(self, request):
        try:
#            if not request.is_ajax():
#                raise BadRequestError('need ajax request')

            self._check_required(request)
            key=self.learn_cache_key(request)
            response=cache.get(key, None)
            if response is None:
                filters, extra=self._get_filters(request)
                foos=self.queryset.filter(*extra, **filters)[:positive(self.limit)]


                #FIXME: please improve me
                if self.distinct:
                    l=set(self.iter_results(foos, self.fields))
                else:
                    l=self.iter_results(foos, self.fields)

                c=''.join(l)
                response=HttpResponse(c, mimetype='text/plain')
                response['sql']=foos._as_sql()
                response.skip_debug_response=True
                patch_response_headers(response, self.timeout)
                cache.set(key, response, self.timeout)

#        except ValueError, e:
#            response = HttpResponseBadRequest('410: %s' % str(e))                
#        except BadRequestError, e:
#            response = HttpResponseBadRequest('411: %s' % str(e))
        except Exception:
            raise
#            response = HttpResponseBadRequest('412: %s %s' % (e.message, filters))            
        return response

    def __call__(self, request):
        return self.handle(request)

    def __str__(self):
        return self.__name__
